function GraphicEngine () {
	// Instance pointer
	var instance = this;
	
	
	//============================== IMAGE LOADER ===================================
	var imageNumber = 0;                 // Number of image in the array
	var imageLoadedNumber = 0;           // Number of completely loaded image
	var imageObjectArray = new Array();  // Image object array
	var imagePathArray = new Array();    // The path of the image array
	
	var globalAlpha = 1;
	
	
	var degToRadRatio = 0.0174532925199433;
	
	
	// Load an image right away ----------------------------------------
	this.LoadImage = function (path) {
		// Check if that image was already existed. Return id if existed.
		for (var i=0; i<imageNumber; i++) {
			if (imagePathArray[i] == path) {
				return i;
			}
		}
		
		// If not existed, load it
		imagePathArray[imageNumber] = path;
		imageObjectArray[imageNumber] = new Image();
		imageObjectArray[imageNumber].src = path;
		imageObjectArray[imageNumber].onload = function () {
			imageLoadedNumber ++;
		}
		
		// Return id
		return imageNumber++;
	}
	// ------------------------------------------------------------------
	
	
	
	
	// Get image by ID --------------------------------------------------
	this.GetImage = function (id) {
		return imageObjectArray[id];
	}
	// ------------------------------------------------------------------
	
	
	
	
	// Load an image when network is free -------------------------------
	this.LoadImageWhenNetworkFree = function (path) {
		
	}
	// ------------------------------------------------------------------
	
	
	// Get image loading progress, return from 0 to 1 -------------------
	this.GetLoadingProgress = function () {
		return imageLoadedNumber / imageNumber;
	}
	// ------------------------------------------------------------------
	
	//============================== IMAGE LOADER ===================================
	
	
	
	
	
	
	//================================ RENDERER =====================================
	this.SetGlobalAlpha = function (alpha) {
		globalAlpha = alpha;
	}
	
	// Clear entire canvas or a portion of it ---------------------------
	this.ClearCanvas = function (context, x, y, w, h) {
		if (x == null) x = 0;
		if (y == null) y = 0;
		if (w == null) w = CANVAS_W;
		if (h == null) x = CANVAS_H;
		
		context.clearRect(x, y, w, h);
	}
	// ------------------------------------------------------------------
	
	
	this.CopyCanvas = function (desContext, sourceCanvas, sx, sy, sw, sh, dx, dy, dw, dh) {
		if (sw == null) sw = sourceCanvas.width;
		if (sh == null) sh = sourceCanvas.height;
		if (dw == null) dw = sourceCanvas.width;
		if (dh == null) dh = sourceCanvas.height;
		
		desContext.drawImage (sourceCanvas, sx, sy, sw, sh, dx, dy, dw, dh);
	}
	
	
	this.FillCanvas = function (context, r, g, b, alpha, x, y, w, h) {
		if (x == null) x = 0;
		if (y == null) y = 0;
		if (w == null) w = CANVAS_W;
		if (h == null) h = CANVAS_H;
		
		if (alpha == null) alpha = 1;
		
		context.globalAlpha = alpha * globalAlpha;
		context.fillStyle = "#" + r.toString(16) + g.toString(16) + b.toString(16);
		context.fillRect(x, y, w, h);
		context.globalAlpha = globalAlpha;
	}
	
	// Draw a loaded image to the canvas context ------------------------
	this.Draw = function (context, imageID, sx, sy, sw, sh, dx, dy, dw, dh, alpha, flipX, flipY, angle) {
		if (alpha == null) alpha = 1;
		
		if (alpha > 1) alpha = 1;
		if (alpha < 0) alpha = 0;
		
		if (flipX == null) flipX = 0;
		if (flipY == null) flipY = 0;
		if (angle == null) angle = 0;
		
		
		
		var image = this.GetImage (imageID);
		
		var save = angle || flipX || flipY;
		
		if (save) context.save();
		
		var signX = (flipX == 0)? 1 : -1;
		var signY = (flipY == 0)? 1 : -1;
		
		if (flipX == 0 && flipY == 0) {
			
		}
		else if (flipX == 1 && flipY == 1) {
			context.translate (sw, sh);
			context.scale (-1, -1);
		}
		else if (flipX == 1 && flipY == 0) {
			context.translate (sw, 0);
			context.scale (-1, 1);
		}
		else if (flipX == 0 && flipY == 1) {
			context.translate (0, sh);
			context.scale (1, -1);
		}
		
		var centerX = dx + dw * 0.5;
		var centerY = dy + dh * 0.5;
		
		if (angle != 0) {
			if (flipX == 0 && flipY == 0) {
				context.translate (centerX, centerY);
				context.rotate (angle * degToRadRatio * signX * signY);
				context.translate (-centerX, -centerY);
			}
			else if (flipX == 1 && flipY == 0) {
				context.translate (sw - centerX, centerY);
				context.rotate (angle * degToRadRatio * signX * signY);
				context.translate (- sw + centerX, -centerY);
			}
			else if (flipX == 0 && flipY == 1) {
				context.translate (centerX, sh - centerY);
				context.rotate (angle * degToRadRatio * signX * signY);
				context.translate (-centerX, - sh + centerY);
			}
			else if (flipX == 1 && flipY == 1) {
				context.translate (sw - centerX, sh - centerY);
				context.rotate (angle * degToRadRatio * signX * signY);
				context.translate (- sw + centerX, - sh + centerY);
			}
		}
		
		dx = dx * signX;
		dy = dy * signY;
		
		
		if (dw > 0 && dh > 0) {
			context.globalAlpha = alpha * globalAlpha;
			context.drawImage (image, sx, sy, sw, sh, dx, dy, dw, dh);
			context.globalAlpha = globalAlpha;
		}
		
		
		if (save) context.restore();
	}
	// ------------------------------------------------------------------
	
	
	// Draw an image quickly without setting param ----------------------
	this.DrawFast = function (context, imageID, dx, dy) {
		var image = this.GetImage (imageID);
		context.globalAlpha = globalAlpha;
		context.drawImage (image, 0, 0, image.width, image.height, dx, dy, image.width, image.height);
		context.globalAlpha = 1;
	}
	// ------------------------------------------------------------------
	
	
	// Set draw mode
	this.SetDrawModeAddActive = function (context, active) {
		if (active == true) {
			context.globalCompositeOperation = "lighter";
		}
		else {
			context.globalCompositeOperation = "source-over";
		}
	}
	
	
	// Draw text --------------------------------------------------------
	// Draw text with RGB color value.
	this.DrawTextRGB = function (context, text, x, y, w, font, size, bold, italic, alignW, alignH, red, green, blue, alpha)
	{
		if (alpha == null) alpha = 1;
		context.globalAlpha = alpha * globalAlpha;
		
		if (font == null) font = "sans-serif";
		if (size == null) size = "12";
		if (bold == true) 
			bold = "bold";
		else
			bold = "";
		if (italic == true) 
			italic = "italic";
		else
			italic = "";
		if (alignW == null)
			context.textAlign = "left";
		else
			context.textAlign = alignW;
		if (alignH == null)	
			context.textBaseline = "top";
		else
			context.textBaseline = alignH;
		
		context.font = bold + " " + italic + " " + size + "px " + font;
		context.fillStyle = "rgba(" + red + "," + green + "," + blue + "," + alpha+ ")";
		
		
		
		if (context.measureText(text).width < w) {
			context.fillText(text, x, y);
			return size * 1.3;
		}
		else {
			// Do lines breaking
			var i              = 0;
			var line           = 0;
			var currentLength  = 0;
			var drawString     = "";
			var tempString     = "";
			var remainString   = text;
			
			while (remainString.length > 0) {
				for (i=0; i<=remainString.length; i++) {
					if (i == remainString.length) {
						context.fillText(drawString + remainString, x, y + line * size * 1.3);
						return (line + 1) * size * 1.3;
					}
					if (remainString.charCodeAt(i) == 32) {
						tempString = remainString.substr(0, i+1);
						if (currentLength + context.measureText(tempString).width < w) {
							drawString += tempString;
							currentLength += context.measureText(tempString).width;
							remainString = remainString.substr(i+1);
						}
						else {
							if (currentLength == 0) {
								return (line + 1) * size * 1.3;
							}
							else {
								context.fillText(drawString, x, y + line * size * 1.3);
								line ++;
								drawString = "";
								currentLength = 0;
							}
						}
						break;
					}
					else if (remainString.charCodeAt(i) == 47) {
						if (remainString.charCodeAt(i + 1) == 110) {
							context.fillText(drawString, x, y + line * size * 1.3);
							line ++;
							drawString = "";
							currentLength = 0;
							remainString = remainString.substr(i+2);
						}
					}
				}
			}
			
			return (line + 1) * size * 1.3;
		}
		
		context.globalAlpha = globalAlpha;
	};
	// ------------------------------------------------------------------
}


var g_graphicEngine = new GraphicEngine();